<?php
/**
 * Template.class.php
 *
 * doitPHP视图类，用于完成对视图文件(仅限html文件)的编译及加载工作
 * @author tommy <streen003@gmail.com>
 * @copyright  Copyright (c) 2010 Tommy Software Studio
 * @link http://www.doitphp.com
 * @license New BSD License.{@link http://www.opensource.org/licenses/bsd-license.php}
 * @version $Id: Template.class.php 1.3 2012-01-18 20:32:00Z tommy $
 * @package core
 * @since 1.0
 */

if (!defined('IN_DOIT')) {
    exit();
}

class Template extends Base {

    /**
     * 视图实例
     *
     * @var object
     */
    protected static $_instance;

    /**
     * 视图目录
     *
     * @var string
     */
    public $view_dir;

    /**
     * 视图编译缓存目录
     *
     * @var string
     */
    public $compile_dir;

    /**
     * 模板标签左侧边限字符
     *
     * @var string
     */
    public $left_delimiter = '<!--\s?{';

    /**
     * 模板标签右侧边限字符
     *
     * @var string
     */
    public $right_delimiter = '}\s?-->';

    /**
     * 模板参数信息
     *
     * @var array
     */
    protected $_options = array();

    /**
     * 视图布局
     *
     * @var string
     */
    public $layout;

    /**
     * 视图缓存文件
     *
     * @var string
     */
    protected $cache_file;

    /**
     * 视图缓存重写开关
     *
     * @var boolean
     */
    protected $cache_status = false;


    /**
     * 构造函数
     *
     * 初始化运行环境,或用于运行环境中基础变量的赋值
     * @access public
     * @return boolean;
     */
    public function __construct() {

        //定义视图目录及编译目录
        $this->view_dir    = VIEW_DIR;
        $this->compile_dir = CACHE_DIR . 'views' . DIRECTORY_SEPARATOR;
    }

    /**
     * 获取视图文件的路径
     *
     * @access protected
     * @param string $file_name    视图名. 注：不带后缀
     * @return string    视图文件路径
     */
    protected function get_view_file($file_name) {

        return $this->view_dir . $file_name . '.html';
    }

    /**
     * 获取视图编译文件的路径
     *
     * @access protected
     * @param string $file_name 视图名. 注:不带后缀
     * @return string    编译文件路径
     */
    protected function get_compile_file($file_name) {

        return $this->compile_dir . $file_name . '.cache.php';
    }

    /**
     * 生成视图编译文件
     *
     * @access protected
     * @param string $compile_file 编译文件名
     * @param string $content    编译文件内容
     * @return void
     */
    protected function create_compile_file($compile_file, $content) {

        //分析编译文件目录
        $compile_dir = dirname($compile_file);
        $this->make_dir($compile_dir);

        $content = "<?php\r\nif(!defined('IN_DOIT')) exit(); ?>\r\n" . $content;

        return file_put_contents($compile_file, $content, LOCK_EX);
    }

    /**
     * 缓存重写分析
     *
     * 判断缓存文件是否需要重新生成. 返回true时,为需要;返回false时,则为不需要
     * @access protected
     * @param string $view_file        视图文件名
     * @param string $compile_file    视图编译文件名
     * @return boolean
     */
    protected function is_compile($view_file, $compile_file) {

        return (is_file($compile_file) && (filemtime($compile_file) >= filemtime($view_file))) ? false : true;
    }

    /**
     * 分析创建目录
     */
    protected function make_dir($dir_name) {

        //参数分析
        if (!$dir_name) {
            return false;
        }

        if (!is_dir($dir_name)) {
            mkdir($dir_name, 0777, true);
        } else if (!is_writable($dir_name)) {
            //更改目录权限
            chmod($dir_name, 0777);
        }
    }

    /**
     * 设置视图变量
     *
     * @access public
     * @param mixed $key    视图变量名
     * @param string $value    视图变量数值
     * @return mixed
     */
    public function assign($key, $value = null) {

        //参数分析
        if(!$key) {
            return false;
        }

        //当$key为数组时
        if(is_array($key)) {
            foreach ($key as $k=>$v) {
                $this->_options[$k] = $v;
            }
        } else {
            $this->_options[$key] = $value;
        }

        return true;
    }

    /**
     * 分析视图文件名
     *
     * @access protected
     * @param mixed $file_name    视图名
     * @return stirng
     */
    protected function parse_file_name($file_name = null) {

        //当参数为空时，默认当前controller当前action所对应的视图文件
        if (is_null($file_name)) {
            //获取当前的controller及action
            $controller_id     = doit::get_controller_id();
            $action_id         = doit::get_action_id();
            //生成默认视图文件
            $file_name         = $controller_id . '/' . $action_id;

        } else {
            //分析视图文件里是否调用非当前controller的视图文件
            if (strpos($file_name, '/') !==  false) {
                $file_name_array     = explode('/', $file_name);
                $file_name           = trim($file_name_array[0]) . '/' . trim($file_name_array[1]);
            } else {
                //当视图类在widget里运行时
                $controller_id       = doit::get_controller_id();
                $file_name           = $controller_id . '/' . $file_name;
            }
        }

        return $file_name;
    }

    /**
     * 加载视图文件
     *
     * 加载视图文件并对视图标签进行编译
     * @access protected
     * @param string $view_file     视图文件及路径
     * @return string                 编译视图文件的内容
     */
    protected function load_view_file($view_file) {

        //分析视图文件是否存在
        if (!is_file($view_file)) {
            trigger_error('The view file: ' . $view_file . ' is not exists!', E_USER_ERROR);
        }

        $view_content = file_get_contents($view_file);

        //编译视图标签
        return $this->handle_view_file($view_content);
    }

    /**
     * 设置视图的主题
     *
     * @access public
     * @param string $theme_name 所要设置的网页模板主题名称
     * @return string 视图的主题名
     */
    public function set_theme($theme_name = 'default') {

        return $this->view_dir = THEME_DIR . $theme_name . DIRECTORY_SEPARATOR;
    }

    /**
     * 设置视图文件布局结构的文件名(layout)
     *
     * layout默认为:main
     * @access public
     * @param string $layout_name     所要设置的layout名称
     * @return string                 layout名称
     */
    public function set_layout($layout_name = null) {

        return $this->layout = $layout_name;
    }

    /**
     * 编译视图标签
     *
     * @access protected
     * @param string $view_content
     * @return string    编译视图文件的内容
     */
    protected function handle_view_file($view_content) {

        //参数分析
        if (!$view_content) {
            return false;
        }

        //正则表达式匹配的模板标签
        $regex_array = array(
        '#'.$this->left_delimiter.'\s*include\s+(.+?)\s*'.$this->right_delimiter.'#is',
        '#'.$this->left_delimiter.'php\s+(.+?)'.$this->right_delimiter.'#is',
        '#'.$this->left_delimiter.'\s?else\s?'.$this->right_delimiter.'#i',
        '#'.$this->left_delimiter.'\s?\/if\s?'.$this->right_delimiter.'#i',
        '#'.$this->left_delimiter.'\s?\/loop\s?'.$this->right_delimiter.'#i',
        );

        ///替换直接变量输出
        $replace_array = array(
        "<?php \$this->render('\\1'); ?>",
        "<?php \\1 ?>",
        "<?php } else { ?>",
        "<?php } ?>",
        "<?php } } ?>",
        );

        //对固定的视图标签进行编辑
        $view_content = preg_replace($regex_array, $replace_array, $view_content);

        //处理if, loop, 变量等视图标签
        $pattern_array = array(
        '#'.$this->left_delimiter.'\s*(\$.+?)\s*'.$this->right_delimiter.'#i',
        '#'.$this->left_delimiter.'\s?(if\s.+?)\s?'.$this->right_delimiter.'#i',
        '#'.$this->left_delimiter.'\s?(elseif\s.+?)\s?'.$this->right_delimiter.'#i',
        '#'.$this->left_delimiter.'\s?(loop\s.+?)\s?'.$this->right_delimiter.'#i',
        '#'.$this->left_delimiter.'\s*(widget\s.+?)\s*'.$this->right_delimiter.'#is',
        );
        $view_content = preg_replace_callback($pattern_array, array($this, 'parse_tags'), $view_content);

        //对编辑后的代码进行压缩,去掉多余的空格,换行,制表符
        $view_content = preg_replace('#\?\>\s*\<\?php\s#s', '', $view_content);
        $view_content = str_replace(array("\r\n", "\n", "\t"), '', $view_content);

        return $view_content;
    }

    /**
     * 分析编辑视图标签
     *
     * @access protected
     * @param string $tag 视图标签
     * @return string
     */
    protected function parse_tags($tag) {

        //变量分析
        $tag = stripslashes(trim($tag[1]));
        //当视图标签为空时
        if(empty($tag)) {
            return '';
        }
        //变量标签处理
        if (substr($tag, 0, 1) == '$') {
            return '<?php echo ' . $this->get_val($tag) . '; ?>';
        } else {
            //分析判断,循环标签
            $tag_sel = array_shift(explode(' ', $tag));
            switch ($tag_sel) {

                case 'if' :
                    return $this->_compile_if_tag(substr($tag, 3));
                    break;

                case 'elseif' :
                    return $this->_compile_if_tag(substr($tag, 7), true);
                    break;

                case 'loop' :
                    return $this->_compile_foreach_start(substr($tag, 5));
                    break;

                case 'widget' :
                    return $this->_compile_widget_tag(substr($tag, 7));
                    break;

                default :
                    return $tag_sel;
                    break;
            }
        }
    }

    /**
     * 处理if标签
     *
     * @access public
     * @param string $tag_args 标签内容
     * @param bool $elseif 是否为elseif状态
     * @return  string
     */
    protected function _compile_if_tag($tag_args, $elseif = false) {

        //分析标签内容
        preg_match_all('#\-?\d+[\.\d]+|\'[^\'|\s]*\'|"[^"|\s]*"|[\$\w\.]+|!==|===|==|!=|<>|<<|>>|<=|>=|&&|\|\||\(|\)|,|\!|\^|=|&|<|>|~|\||\%|\+|\-|\/|\*|\@|\S#i', $tag_args, $match);
        $tokens = $match[0];

        //当$match[0]不为空时
        $token_array = array();
        foreach ($match[0] as $vaule) {
            $token_array[] = $this->get_val($vaule);
        }
        $token_str = implode(' ', $token_array);
        unset($token_array);

        return ($elseif === false) ? '<?php if (' . $token_str . ') { ?>' : '<?php } else if (' . $token_str . ') { ?>';
    }

    /**
     * 处理foreach标签
     *
     * @access public
     * @param string $tag_args 标签内容
     * @return string
     */
    protected function _compile_foreach_start($tag_args) {

        //分析标签内容
        preg_match_all('#(\$.+?)\s+(.+)#i', $tag_args, $match);
        $loop_var = $this->get_val($match[1][0]);

        return '<?php if (is_array(' . $loop_var . ')) { foreach (' . $loop_var . ' as ' . $match[2][0] . ') { ?>';
    }

    /**
     * 处理widget标签
     *
     * @access public
     * @param string $tag_args 标签内容
     * @return string
     */
    protected function _compile_widget_tag($tag_args) {

        //判断是否为参数传递标签
        $pos = strpos($tag_args, '$');

        if ($pos !== false) {
            $widget_id = trim(substr($tag_args, 0, $pos));
            $params    = $this->get_val(trim(substr($tag_args, $pos)));

            return '<?php Controller::widget(\'' . $widget_id . '\', ' . $params . '); ?>';
        }

        return '<?php Controller::widget(\'' . $tag_args . '\'); ?>';
    }

    /**
     * 处理视图标签中的变量标签
     *
     * @access protected
     * @param string $val 标签名
     * @return string
     */
    protected function get_val($val) {

        if (strpos($val, '.') === false) {
            return $val;
        }

        $val_array = explode('.', $val);
        $_var_name = array_shift($val_array);

        return $_var_name . '[\'' . implode('\'][\'', $val_array) . '\']';
    }

    /**
     * 加载局部视图
     *
     * @access protected
     * @param string $file_name      局部视图文件名
     * @return string                编译文件路径
     */
    protected function render($file_name) {

        //参数分析
        if (!$file_name) {
            return false;
        }

        //分析视图文件名
        $file_name       = $this->parse_file_name($file_name);

        //获取视图文件及编译文件
        $view_file       = $this->get_view_file($file_name);
        $compile_file    = $this->get_compile_file($file_name);

        //分析视图编译文件是否需要重新生成
        if ($this->is_compile($view_file, $compile_file)) {
            $view_content = $this->load_view_file($view_file);
            //重新生成编译缓存文件
            $this->create_compile_file($compile_file, $view_content);
        } else {
            ob_start();
            include $compile_file;
            $view_content = ob_get_clean();
        }

        echo $view_content;
    }

    /**
     * 分析视图缓存文件名
     *
     * @access protected
     * @param string $cache_id 缓存ID
     * @return string
     */
    protected function parse_cache_file($cache_id) {

        return CACHE_DIR . 'html/' . doit::get_controller_id() . '/' . md5($cache_id) . '.action.html';
    }

    /**
     * 分析视图缓存文件是否需要重新创建
     *
     * @access public
     * @param string $cache_id 缓存ID
     * @param integer $lifetime 缓存文件生存周期, 默认为一年
     * @return boolean
     */
    public function cache($cache_id = null, $lifetime = null) {

        //参数分析
        if (is_null($cache_id)) {
            $cache_id = doit::get_action_id();
        }
        if (is_null($lifetime)) {
            //默认缓存生命周期为:一年
            $lifetime = 31536000;
        }

        //获取缓存文件
        $cache_file = $this->parse_cache_file($cache_id);
        if (is_file($cache_file) && (filemtime($cache_file) + $lifetime >= time())) {
            include $cache_file;
            exit();
        }

        $this->cache_status = true;
        $this->cache_file   = $cache_file;

        return true;
    }

    /**
     * 创建缓存文件
     *
     * @access protected
     * @param string $cache_file 缓存文件名
     * @param string $content 缓存文件内容
     * @return boolean
     */
    protected function create_cache_file($cache_file, $content = null) {

        //参数分析
        if (!$cache_file) {
            return false;
        }
        if (is_null($content)) {
            $content = '';
        }

        //分析缓存目录
        $cache_dir = dirname($cache_file);
        $this->make_dir($cache_dir);

        return file_put_contents($cache_file, $content, LOCK_EX);
    }

    /**
     * 显示视图文件
     *
     * @access public
     * @param string $file_name    视图名
     * @return void
     */
    public function display($file_name = null) {

        //分析视图变量
        if (!empty($this->_options)) {
            extract($this->_options, EXTR_PREFIX_SAME, 'data');
            //清空不必要的内存占用
            $this->_options = array();
        }

        //判断layout视图变量
        if ($this->layout) {
            $layout_file     = $this->view_dir . 'layout/' . $this->layout . '.html';
            $layout_state    = is_file($layout_file) ? true : false;
        } else {
            $layout_state    = false;
        }

        //分析视图文件名
        $file_name = $this->parse_file_name($file_name);

        //获取视图文件及编译文件
        $view_file      = $this->get_view_file($file_name);
        $compile_file   = $this->get_compile_file($file_name);

        //分析视图编译文件是否需要重新生成
        if ($this->is_compile($view_file, $compile_file)) {
            $view_content = $this->load_view_file($view_file);
            //重新生成编译缓存文件
            $this->create_compile_file($compile_file, $view_content);
        }

        if (!$layout_state) {
            //加载编译缓存文件
            ob_start();
            include $compile_file;
            $html_content = ob_get_clean();
        } else {

            //加载layout文件
            $layout_compile_file = $this->get_compile_file('layout/' . $this->layout);
            if ($this->is_compile($layout_file, $layout_compile_file)) {
                //重新生成layout视图编译文件
                $layout_content = $this->load_view_file($layout_file);
                $this->create_compile_file($layout_compile_file, $layout_content);
            }

            //获取视图编译文件内容
            ob_start();
            include $compile_file;
            $content = ob_get_clean();

            //获取所要显示的页面的视图编译内容
            ob_start();
            include $layout_compile_file;
            $html_content = ob_get_clean();
        }

        //显示视图内容
        echo $html_content;

        //创建视图缓存文件
        if ($this->cache_status == true) {
            $this->create_cache_file($this->cache_file, $html_content);
        }
    }

    /**
     * 析构函数
     *
     * 用于程序运行结束后"打扫战场"
     * @access public
     * @return void
     */
    public function __destruct() {
        //清空不必要的内存占用
        $this->_options = array();
    }

    /**
     * 单件模式调用方法
     *
     * @static
     * @var object
     */
     public static function getInstance(){

         if (!self::$_instance instanceof self) {
             self::$_instance = new self();
         }

        return self::$_instance;
    }
}